<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>[2.15]-演进：新型计划任务续篇 | PhalApi(π框架) - PHP轻量级开源接口框架 - 助你创造价值！</title>
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
    <meta content="telephone=no" name="format-detection">
    <meta content="email=no" name="format-detection">
    <meta name="Author" content="PhalApi,ecitlm,dogstar">
    <meta name="description" content="[2.15]-演进：新型计划任务续篇，PhalApi是一个PHP轻量级开源接口框架，致力于快速开发接口服务。支持HTTP/SOAP/RPC等协议，可用于搭建接口/微服务/RESTful接口/Web Services。我们不断更新，保持生气；为接口负责，为开源负责！并承诺永久免费！">
    <meta name="keywords" content="PhalApi,phalapi,phalapi接口开发,后台接口开发,后台接口开发框架,接口开发,接口框架,开源接口框架,PHP后台接口开发,PHP接口开发,PHP接口框架,PHP后台接口框架,phalapi官网,PHP接口框架,php接口开发框架,php接口开发,php web框架,π框架,π开发框架,π接口框架,π接口开发框架,api接口,php接口,,PHP接口框架,phalapi文档,phalapi wiki,PhalApi文档,phalapi在线文档,phalapi官方文档">

    <link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" media="screen">
    <link rel="stylesheet" type="text/css" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css" media="screen">
    <link rel="stylesheet" href="../css/main.css" />
    <link rel="stylesheet" href="../css/loading_bar.css" />
    <link rel="icon" href="http://webtools.qiniudn.com/dog_catch.png" type="image/x-icon" />
    <script src="../js/pace.min.js"></script>
	
	<!-- 代码高亮 -->
	<link rel="stylesheet" href="../css/highlight.min.css">
	<script src="../js/highlight.min.js"></script>
	<script>hljs.initHighlightingOnLoad();</script>
    
</head>

<body>
    <!-- navbar start -->
    <div class="navbar navbar-default navbar-fixed-top" id="mainnav" role="navigation">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand website_name" href="/"><!-- <span>PhalApi</span> --><strong>PhalApi</strong></a>
            </div>
            <div class="navbar-collapse collapse" aria-expanded="false" style="height: 1px;">
                <ul class="nav navbar-nav navbar-right" id="nav_bar">
                    <li><a href="/"><span class="glyphicon glyphicon-home"></span></a></li>
                    <li><a href="http://qa.phalapi.net/">社区</a></li>
                    <li><a href="/download.html">下载</a></li>
                    <li class="active"><a href="/wikis/">文档</a></li>
                    <li><a href="http://www.ituring.com.cn/book/2405">书籍</a></li>
                    <li><a href="http://demo.phalapi.net/">体验</a></li>
                    <li><a href="/about.html">关于</a></li>
                    <li><a href="/donate.html">贡献</a></li>
                    <li><a href="/wikis/en/">English</a></li>
                </ul>
            </div>
            <!--/.nav-collapse -->
        </div>
    </div>
    <!-- navbar end -->


    <div id="content">
        <div class="container">
            <div class="row row-md-flex row-md-flex-wrap">
                <h4><a href="/wikis/%5B2.14%5D-%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91%EF%BC%9A%E6%84%8F%E5%9B%BE%E5%AF%BC%E5%90%91%E7%BC%96%E7%A8%8B%E4%B8%8B%E7%9A%84%E6%8E%A5%E5%8F%A3%E5%BC%80%E5%8F%91.html">上一章</a>   <a href="/wikis/">文档首页</a>   <a href="/wikis/%5B2.16%5D-%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%EF%BC%9A%E5%BA%94%E5%AF%B9%E5%A4%8D%E6%9D%82%E9%A2%86%E5%9F%9F%E4%B8%9A%E5%8A%A1%E7%9A%84Domain%E5%B1%82-.html">下一章</a></h4>
<hr />
<p><em>计算机科学中的任何问题都可以用另外的间接层解决，但是这通常会引发另一个问题。  -- David Wheeler</em>  </p>
<h2>2.15.1 新型计划任务回顾</h2>
<p>在 <a href="/wikis/%5B1.31%5D-%E6%96%B0%E5%9E%8B%E8%AE%A1%E5%88%92%E4%BB%BB%E5%8A%A1%EF%BC%9A%E4%BB%A5%E6%8E%A5%E5%8F%A3%E5%BD%A2%E5%BC%8F%E5%AE%9E%E7%8E%B0%E7%9A%84%E8%AE%A1%E5%88%92%E4%BB%BB%E5%8A%A1.html">[1.31]-新型计划任务：以接口形式实现的计划任务</a> 一章中，我们讨论了PhalApi中对计划任务的设计和底层实现。 </p>
<p>但对于很多应用，很多项目，或者很多同学来说，仍然比较广泛，不能直接使用。<br />
这一章则专门为此而进行演进，并提供最终可用的计划任务调度，同时我们也会阐明如何进行扩展定制。</p>
<p>也就是说，这一章将提供Task扩展类库的统一调度方式，以便在启动crontab任务后，可以通过数据库简单配置，即可执行各种任务。  </p>
<h2>2.15.2 最终调度的方式：crontab</h2>
<p>出于对业务的考虑，我们首先需要明确此crontab调度方式所支持的功能，它应该包括但不限于：  </p>
<ul>
<li>1、通过简单的数据库配置，即可启动一个新的任务</li>
<li>2、具备循环调度的能力，并能初步防止并发调度</li>
<li>3、可以对异常的任务进行修复</li>
<li>4、优先执行太远未执行的任务</li>
<li>5、支持本地和远程两种调度方式、三种MQ类型，以及扩展的能力</li>
</ul>
<h2>2.15.3 核心时序图与分层</h2>
<p>在原来的时序图基础上，我们可以进行演进的设计，追加了统一的调度后如下所示：<br />
<img src="http://webtools.qiniudn.com/task_6.jpg" alt="a pic" />   </p>
<p>通过上面详细的时序图，我们可以发现里面的设计是出于这样的分层考虑：  </p>
<table class="table table-bordered">
<thead>
<tr>
<th>序号</th>
<th>层</th>
<th>关键操作</th>
<th>说明</th>
<th>如何使用</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>启动脚本</td>
<td>crontab.php</td>
<td>操作crontab执行的脚本</td>
<td>客户端可以进行必要的初始化工作</td>
</tr>
<tr>
<td>2</td>
<td>进程级</td>
<td>Task_Progress::run()</td>
<td>根据进程配置的数据库表，进行循环调度</td>
<td>不需要改动，直接使用</td>
</tr>
<tr>
<td>3</td>
<td>触发器</td>
<td>Task_Trigger::fire()</td>
<td>进行计划任务调度的上下文环境，用于指定runner和mq类型</td>
<td>客户端也可进行定制扩展，进行必要的操作</td>
</tr>
<tr>
<td>4</td>
<td>MQ消费与调度</td>
<td>Task_MQ::pop()和Task_Runner::go()</td>
<td>不断消费MQ队列，并依次进行调度</td>
<td>不需要改动，直接使用，也可扩展</td>
</tr>
<tr>
<td>5</td>
<td>计划任务服务</td>
<td>PhalApi_Api::doSth()</td>
<td>执行计划任务服务</td>
<td>由客户端按接口形式实现</td>
</tr>
</tbody>
</table>
<p>虽然上面的层级，初看起来有点多，但我们再次验证了计算机那个伟大的定论：计算机的任何问题都可以通过一个中间层来解决。  </p>
<p>由此看出，上面的层级其实相当于：</p>
<pre><code>客户端初始化 --&gt; 直接使用 --&gt; 自由组合与操作 --&gt; 直接使用 --&gt; 任务服务实现</code></pre>
<h2>2.16.4 进程配置的数据库表设计</h2>
<pre><code class="language-sql">CREATE TABLE `phalapi_task_progress` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(200) DEFAULT '' COMMENT '任务标题',
  `trigger_class` varchar(50) DEFAULT '' COMMENT '触发器类名',
  `fire_params` varchar(255) DEFAULT '' COMMENT '需要传递的参数，格式自定',
  `interval_time` int(11) DEFAULT '0' COMMENT '执行间隔，单位：秒',
  `enable` tinyint(1) DEFAULT '1' COMMENT '是否启动，1启动，0禁止',
  `result` varchar(255) DEFAULT '' COMMENT '运行的结果，以json格式保存',
  `state` tinyint(1) DEFAULT '0' COMMENT '进程状态，0空闲，1运行中，-1异常退出',
  `last_fire_time` int(11) DEFAULT '0' COMMENT '上一次运行时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;</code></pre>
<p>对此表的关键字段说明如下：</p>
<table class="table table-bordered">
<thead>
<tr>
<th>字段</th>
<th>说明</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>trigger_class</td>
<td>触发器的类名</td>
<td>须实现Task_Progress_Trigger::fire($params)接口</td>
</tr>
<tr>
<td>fire_params</td>
<td>触发器的参数</td>
<td>加传给Task_Progress_Trigger::fire()函数的参数，格式为：service&amp;MQ类名&amp;runner类名</td>
</tr>
<tr>
<td>interval_time</td>
<td>执行间隔</td>
<td>单位为秒</td>
</tr>
<tr>
<td>enable</td>
<td>是否启动</td>
<td>此字段禁止时，将不再执行</td>
</tr>
<tr>
<td>state</td>
<td>进程状态</td>
<td>当此状态一直为异常或者运行且超过1天时，系统会进行修复，即重置为空闲状态</td>
</tr>
</tbody>
</table>
<p>其中，对于fire_params参数，MQ类名和runner类名可选，以下是一些示例：</p>
<pre><code class="language-php">//示例1：完整的配置
//fire_params=Task_Demo.DoSth&amp;Task_MQ_DB&amp;Task_Runner_Local
$mq = new Task_MQ_DB();
$runner = new Task_Runner_Local($mq);
$runner-&gt;go('Task_Demo.DoSth');

//示例2：使用默认的Runner
//fire_params=Task_Demo.DoSth&amp;Task_MQ_DB
$mq = new Task_MQ_DB();
$runner = new Task_Runner_Local($mq);  //默认使用本地Runner
$runner-&gt;go('Task_Demo.DoSth');

//示例3:使用默认的MQ和默认的Runner
//fire_params=Task_Demo.DoSth
$mq = new Task_MQ_Redis(); //默认使用redis的MQ
$runner = new Task_Runner_Local($mq);  //默认使用本地Runner
$runner-&gt;go('Task_Demo.DoSth');

//示例4：使用自定义的MQ和Runner
//fire_params=Task_Demo.DoSth&amp;My_MQ&amp;My_Runner
class My_MQ implements Task_MQ {
     // ...
}
class My_Runner extends Task_Runner {
     // ...
}
$mq = new My_MQ();
$runner = new My_Runner($mq);
$runner-&gt;go('Task_Demo.DoSth');</code></pre>
<h2>2.15.5 运行效果</h2>
<p>最终的效果就是，我们通过这样两行简单的代码，即可实现一系列复杂的任务调度：</p>
<pre><code class="language-php">$progress = new Task_Progress();
$progress-&gt;run();</code></pre>
<p>让我们来看下这样设计的运行效果吧！看下这两行代码背后所产生的魔力。  </p>
<p>首先，我们先添加两条计划任务：</p>
<pre><code class="language-sql">INSERT INTO `phalapi_task_progress` VALUES ('1', 'test demo', 'Task_Progress_Trigger_Common', 'Task_Demo.DoSth&amp;Task_MQ_File&amp;Task_Runner_Local', '300', '1', '', '0', '0');
INSERT INTO `phalapi_task_progress` VALUES ('2', 'test ok', 'Task_Progress_Trigger_Common', 'Default.Index&amp;Task_MQ_DB&amp;Task_Runner_Local', '100', '1', '', '0', '0');</code></pre>
<p>然后，伪造一些MQ：</p>
<pre><code class="language-php">INSERT INTO `phalapi_task_mq_0` VALUES ('8', 'Default.Index', '', '0', '');</code></pre>
<p>最后，生成单元测试：</p>
<pre><code class="language-php">&lt;?php
class PhpUnderControl_TaskProgress_Test extends PHPUnit_Framework_TestCase
{
    public $taskProgress;

    protected function setUp()
    {
        parent::setUp();

        $this-&gt;taskProgress = new Task_Progress();
    }

    /**
     * @group testRun
     */
    public function testRun()
    {
        $rs = $this-&gt;taskProgress-&gt;run();
    }

}
</code></pre>
<p>并执行之：</p>
<pre><code class="language-bash">$ phpunit ./Task_Progress_Test.php

[1 - 0.06666s]SELECT id, title FROM phalapi_task_progress WHERE (state != ?) AND (last_fire_time &lt; ?) AND (enable = ?) ORDER BY last_fire_time ASC; -- 0, 1431965153, 1&lt;br&gt;
[2 - 0.07002s]SELECT id, title, trigger_class, fire_params FROM phalapi_task_progress WHERE (state = 0) AND (interval_time + last_fire_time &lt; ?) AND (enable = ?); -- 1432051553, 1&lt;br&gt;
[3 - 0.06549s]SELECT enable, state FROM phalapi_task_progress WHERE (id = '1');&lt;br&gt;
[4 - 0.07432s]UPDATE phalapi_task_progress SET state = 1 WHERE (id = '1');&lt;br&gt;
[5 - 0.06469s]UPDATE phalapi_task_progress SET result = '{\"total\":0,\"fail\":0}', state = 0, last_fire_time = 1432051553 WHERE (id = '1');&lt;br&gt;
[6 - 0.06746s]SELECT enable, state FROM phalapi_task_progress WHERE (id = '2');&lt;br&gt;
[7 - 0.07043s]UPDATE phalapi_task_progress SET state = 1 WHERE (id = '2');&lt;br&gt;
[8 - 0.06673s]SELECT id, params FROM phalapi_task_mq_0 WHERE (service = 'Default.Index') ORDER BY id ASC LIMIT 0,10;&lt;br&gt;
[9 - 0.48185s]DELETE FROM phalapi_task_mq_0 WHERE (id IN ('8'));&lt;br&gt;
[10 - 0.06514s]SELECT id, params FROM phalapi_task_mq_0 WHERE (service = 'Default.Index') ORDER BY id ASC LIMIT 0,10;&lt;br&gt;
[11 - 0.50694s]UPDATE phalapi_task_progress SET result = '{\"total\":1,\"fail\":0}', state = 0, last_fire_time = 1432051553 WHERE (id = '2');&lt;br&gt;

Time: 1.98 seconds, Memory: 6.50Mb

OK (1 test, 0 assertions)</code></pre>
<p>查看对比一下数据库，目前发现运行良好！  </p>
<p>提交代码，保存文档，收工睡觉！</p>
<h2>2.15.6 演进的乐趣</h2>
<p>得益于前期良好的设计以及底层支持，我们发现，在提供这样一种统一的调度方式是非常方便的。  </p>
<p>不仅如此，如果你明白了其中的设计，需要进行定制和扩展也是非常方便的。也就是说，我们不仅提供了一种具体实际可用的方式，也提供了广阔自由的扩展空间。具体与抽象，两者仍可得。  </p>
<p>然而，这一切不仅依赖于良好的设计，还依赖于测试驱动开发下的浮现式设计。</p>
<hr />
<h4><a href="/wikis/%5B2.14%5D-%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91%EF%BC%9A%E6%84%8F%E5%9B%BE%E5%AF%BC%E5%90%91%E7%BC%96%E7%A8%8B%E4%B8%8B%E7%9A%84%E6%8E%A5%E5%8F%A3%E5%BC%80%E5%8F%91.html">上一章</a>   <a href="/wikis/">文档首页</a>   <a href="/wikis/%5B2.16%5D-%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%EF%BC%9A%E5%BA%94%E5%AF%B9%E5%A4%8D%E6%9D%82%E9%A2%86%E5%9F%9F%E4%B8%9A%E5%8A%A1%E7%9A%84Domain%E5%B1%82-.html">下一章</a></h4><div style="float: left">
<h4>
<a href="http://qa.phalapi.net/">还有疑问？欢迎到社区提问！</a>
</h4>
</div>
            </div>
        </div>
    </div>

 <!-- 广告位 -->
<div class="grid-wrapper desktop-only">
	<p align="center">
		<a href="http://7xiz2f.com1.z0.glb.clouddn.com/%E6%88%91%E7%9A%84%E5%90%8D%E5%AD%97%E5%8F%AB%EF%BC%9A%CF%80%E6%A1%86%E6%9E%B6%20-%20PhalApi%202016%E5%B9%B4%E5%BA%A6%E5%BC%80%E6%BA%90%E6%80%BB%E7%BB%93%20-%20%E5%AE%98%E6%96%B9%E5%87%BA%E5%93%81.pdf" target="blank"><img width="950" height="100" src="http://7xiz2f.com1.z0.glb.clouddn.com/ad_20170104.png"></a>
		<a href="http://www.itran.cc/" target="blank"><img width="950" height="100" src="http://7xslqv.com1.z0.glb.clouddn.com/images/9/69/b88bc92455dc1239f9a5bf8d72929.png"></a>
		<a href="http://web-tools.phalapi.net/" target="blank"><img width="950" height="100" src="http://7xslqv.com1.z0.glb.clouddn.com/images/7/d0/88f523566c482296aecc43d185ca2.png"></a>
	</p>
</div>

        <!-- footer -->
        <div class="footer">
            <div class="link container ">
                <div class=" col-xs-7 col-sm-7 col-md-7 col-lg-7 ">
                    <h3>PhalApi (π框架)</h3>
                    <p>一个轻量级PHP开源接口框架，专注于接口服务开发，支持HTTP/SOAP/RPC协议，拥有自动生成的在线文档、多种开发语言的客户端SDK包以及可重用的扩展类库，可用于快速搭建微服务、RESTful接口或Web
                        Services。</p>
                    <!-- <p>:</p> -->
                    <span>友情链接:</span>
                    <a href="https://www.phalapi.net/" target="_blank"> PhalApi </a>
                    <a href="http://www.oschina.net/" target="_blank"> 开源中国 </a>
                    <a href="https://www.itran.cc/" target="_blank">艾翻译</a>
                    <a href="http://www.phalconphp.com/en/" target="_blank">Phalcon</a>
                    <a href="https://phpunit.de/manual/3.7/zh_cn/automating-tests.html" target="_blank">PHPUnit</a>
                    <a href="http://www.thoughtworks.com/cn/" target="_blank">ThoughtWorks</a>
                    <a href="mailto:chanzonghuang@gmail.com">友链交换</a>

                </div>
                <div class=" col-xs-5 col-sm-5 col-md-5 col-lg-5 contact_us ">
                    <h3>联系我们</h3>
                    <a href="https://github.com/phalapi/phalapi"><img src="../images/github.png" alt="github" /></a>
                    <a href="http://weibo.com/p/100808d236e99beb645bfb56ed1c37dde9b8bd?k=phalapi%E5%BC%80%E6%BA%90%E6%A1%86%E6%9E%B6&from=501&_from_=huati_topic"><img src="../images/weibo.png"  alt="weibo" /></a>
                    <a href="http://7xslqv.com1.z0.glb.clouddn.com/images/1/b0/894fde762850cb11250253f7d5ede.png"><img src="../images/zfb.png"  alt="zfb" /></a>
                    <a href="https://jq.qq.com/?_wv=1027&k=4A6reum"><img src="../images/qq.png"  alt="qq" /></a>
					<a href="http://git.oschina.net/dogstar/PhalApi"><img src="../images/git-oschina.png"  alt="git-oschina" /></a>
                </div>
                <div class="cls"></div>
                <div class="text-center copy_right"> ©2015-2017 PhalApi All Rights Reserved. <a href="http://www.miitbeian.gov.cn" target="_blank">粤ICP备15028808号</a>

				<script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1255326144'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/z_stat.php%3Fid%3D1255326144%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));</script>
                </div>
            </div>
        </div>
    </div>


    <a href="https://github.com/phalapi"><img id="gitHub_fllow" style="position: fixed; top: 0; right: 0; border: 0;z-index:9999" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>


    <script src="../js/jquery-1.11.1.min.js"></script>
    <script src="../js/bootstrap.min.js"></script>
</body>

</html>